Skip to content

fix(browser-session): require aggregate-issued lifecycle request authority - #317

Draft
seonghobae wants to merge 50 commits into
feat/privacy-presentation-identityfrom
fix/browser-session-lifecycle-request-capability
Draft

fix(browser-session): require aggregate-issued lifecycle request authority#317
seonghobae wants to merge 50 commits into
feat/privacy-presentation-identityfrom
fix/browser-session-lifecycle-request-capability

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Prerequisite repair for #312 and #314/#316, stacked on #229 exact 6d87dff5dc572fbd74d06309d574a998f23cf02f.

Current exact head is f73cc5def267b99f43986cd3c504b86cb3d489d7. This PR remains Draft during source repair and is not merge-ready until repository contracts, canonical formatting, locked tests, strict Clippy, rustdoc/API docs, production function/line/region/branch coverage exactly 100%, and fresh independent review all accept the same exact head.

Active Browser Session blockers:

  • 5176486914: duplicate/unsettled create recovery loses aggregate-issued attempt_epoch; accepted same-valued ownership and a later failed attempt must remain separate lifecycle facts.
  • 5177440777: CreateFailedUncertain(Some/None) must preserve the exact aggregate-issued create attempt identity even when no complete handle exists.
  • 5178435963: AuthorizedContextOperationRequest must carry the already-validated BrowserContextEpoch as non-authorizing execution/provenance correlation; stale authority remains zero-I/O.
  • 5178943747: destroy request and unproven-destruction evidence must preserve the exact validated BrowserContextEpoch; command ACK is not destruction proof.
  • 5179537115: RecoveryRequired/TransportLost needs a purpose-bounded consuming handoff preserving the exact same adapter plus exact recovery evidence without restoring ordinary mutation authority. No raw P, second adapter, Drop I/O, or id reconstruction.
  • 5177951332: proven-destroyed history must not make command-authority hot state grow unbounded or create validation degrade with historical tombstones. Keep live/uncertain duplicate detection and stale-authority rejection; separate audit history from hot ownership state.
  • 5180191348: Browser Session maps DisposableIsolationId one-to-one to WebDriver BiDi browser.UserContext, but production currently rejects UTF-8 byte length >4096 although the 9 September 2026 W3C WD defines browser.UserContext as text and no such protocol limit. Commit 9818ada98cbdb9ed4054e96b7c7c4f3e52d51f3a adds intentional RED user_context_identity_length.rs; otherwise-valid remote identity must remain losslessly representable for exact destroy/recovery. Resource bounds belong at a cited protocol/frame/runtime/deployment boundary, not an uncited domain constant.
  • exact Clippy/dead-mutability failures from predecessor full CI remain to be repaired; do not weaken lint or coverage gates.

Standards/repository-contract repair completed on this lineage:

  • ADR 0114 records the W3C 9 September 2026 Working Draft (WD-webdriver-bidi-20260909, previous 3 September) without silently repinning the separately qualified runtime revision.
  • 9c17e7c8d6e491a2610e7cc5b48089a6980bfebe removes the stale repository-contract requirement for WD-webdriver-bidi-20260824 and gates the ADR on WD-webdriver-bidi-20260909 instead.
  • f73cc5def267b99f43986cd3c504b86cb3d489d7 synchronizes docs/traceability/browser-session-lifecycle-authority.md, records the arbitrary 4096-byte user-context ceiling as an open active-branch mismatch, and keeps standards freshness separate from runtime qualification.
  • docs/product-technical-gap-baseline.md still contains the stale 24 August continuity note and must be currentized before documentation is code-current; do not treat the trace/ADR repair alone as documentation completion.

Already repaired lineage remains valid: RecoveryRequired sibling exact-handle evidence (5175575251), non-consuming failed finish(&mut self) same-owner retry (5175813759), structural single-adapter binding with no raw port accessor, aggregate-issued create attempt/completion boundary, redacted adapter Debug, idempotent transport-loss evidence, and non-I/O abandonment signaling.

Hosted verification:

  • latest non-skipped full CI is predecessor exact 56a96ad8407b418d1775cfdf091519b57ad1e893 / run 34578759212: repository contracts 176/176 GREEN, fmt GREEN, locked workspace tests GREEN, strict Clippy RED (double_must_use plus unnecessary mut), rustdoc not reached.
  • predecessor coverage artifact 10190957720 (sha256:a6757604038ab58d7d4d18a359f5e3d5d1c6495ddd40d37586298ccbe59eb155) measured branches 800/800, functions 699/699, lines 5920/5924, regions 7314/7318. Repair semantics; no exclusions or coverage-only branches.
  • exact 9818ada... push CI 34614061293 and eb2701b... push CI 34614356319 were Draft-policy skipped, not GREEN evidence. No newer exact-head GREEN is claimed for f73cc5d....
  • local isolated execution of the intentional user-context RED was attempted on the predecessor lineage, but the execution environment could not resolve github.com; this is not a code result and is not promoted as verification evidence.

Shortest causal path: create-attempt recovery correlation → operation/destroy epoch correlation → same-adapter recovery takeover → bounded hot ownership state → remove/replace arbitrary user-context cap with an authoritative boundary and turn user_context_identity_length.rs GREEN → product-baseline sync → Clippy/dead-mutability repair → repository contracts → fmt → locked tests → strict Clippy → rustdoc/API docs → function/line/region/branch 100% → fresh independent review → ordinary #317 adoption → #316 non-force restack/remote-liveness reconciliation → explicitly qualified Chromium navigation/interaction/destruction/post-condition evidence.

Buyer acceptance beyond this foundation remains #316 pending→accepted/quarantined integration, durable crash/process-restart recovery, real browser-observed destruction/post-conditions on an explicitly qualified Chromium lane, #299 3/3 replay, and protected-main release/SBOM/provenance/reproducibility/rollback.

No #229/main/#316 source mutation, force/destructive restack, self-approval, bypass, workflow/ruleset/secret change, sandbox weakening, provider/model pin, coverage weakening, merge, tag, or release.

@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: d786d13b-2d0e-4bcf-b949-eeb64ea36473

📥 Commits

Reviewing files that changed from the base of the PR and between 6d87dff and 9cde981.

📒 Files selected for processing (10)
  • crates/originweave-browser-session/src/lib.rs
  • crates/originweave-browser-session/tests/destroy_failure_requires_recovery.rs
  • crates/originweave-browser-session/tests/lifecycle_port_authority.rs
  • crates/originweave-browser-session/tests/lifecycle_port_preflight_side_effect.rs
  • crates/originweave-browser-session/tests/lifecycle_port_same_id_spoof.rs
  • crates/originweave-browser-session/tests/sequential_incarnation_reuse.rs
  • docs/adr/0114-browser-session-disposable-context-authority.md
  • docs/traceability/browser-session-lifecycle-authority.md
  • docs/uml/browser-session-lifecycle-authority.md
  • tests/test_browser_session_lifecycle_contract.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

BrowserSession이 구체적인 포트를 소비하는 BoundBrowserSession을 제공합니다. 생성·삭제 요청은 aggregate 내부에서 opaque 타입으로 발행됩니다. 포트 계약, 테스트, 권한 문서와 UML이 새 호출 흐름에 맞게 갱신되었습니다.

Changes

라이프사이클 포트 권한

Layer / File(s) Summary
요청 계약과 바운드 세션 API
crates/originweave-browser-session/src/lib.rs
DisposableContextCreateRequestDisposableContextDestroyRequest를 추가했습니다. DisposableContextPort는 요청 객체를 받습니다. bind_lifecycle_port는 aggregate와 구체 포트를 BoundBrowserSession으로 결합합니다.
구현 상태와 포트 호출 검증
crates/originweave-browser-session/src/lib.rs, crates/originweave-browser-session/tests/*
내부 테스트 포트와 통합 테스트를 새 API로 변경했습니다. 바운드 포트만 생성·삭제 호출을 받으며, 바인딩 중 adapter 콜백은 실행되지 않고, 다른 포트로 교체할 수 없음을 검증합니다. 순차 incarnation과 삭제 실패 복구 검사도 갱신했습니다.
권한 설계 문서 갱신
docs/adr/0114-browser-session-disposable-context-authority.md
선형 lifecycle-port binding, opaque 요청, 콜백 금지, 대체 포트 차단 및 마이그레이션 내용을 반영했습니다.
추적성, UML, 계약 테스트 갱신
docs/traceability/browser-session-lifecycle-authority.md, docs/uml/browser-session-lifecycle-authority.md, tests/test_browser_session_lifecycle_contract.py
추적성 문서와 UML을 새 호출 흐름으로 변경했습니다. 계약 테스트는 새 공개 타입과 메서드, 제거된 API, 생성자 비공개 상태를 검사합니다.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant BoundBrowserSession
  participant DisposableContextPort
  Caller->>BoundBrowserSession: bind_lifecycle_port(port)
  Caller->>BoundBrowserSession: create_disposable_context()
  BoundBrowserSession->>DisposableContextPort: create_disposable_context(request)
  Caller->>BoundBrowserSession: destroy_disposable_context(authority)
  BoundBrowserSession->>DisposableContextPort: destroy_disposable_context(request)
Loading

Merge Risk: ⚪ Minimal · up to 9cde9

No actionable current-head defect remains; complete the normal required checks before merging.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 66 functions across 7 files. (3 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 aggregate-issued lifecycle request authority를 요구하도록 변경한 핵심 API 및 권한 설계를 정확하고 간결하게 설명합니다.
Full details: Docstring Coverage

Explanation

Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 66 functions across 7 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/browser-session-lifecycle-request-capability

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae
seonghobae marked this pull request as draft September 10, 2026 19:22
@seonghobae
seonghobae marked this pull request as ready for review September 10, 2026 19:27
@seonghobae
seonghobae marked this pull request as draft September 10, 2026 19:33

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head security finding on 97e0a4d875166ad733e78c1d3f213454ee615f01: the new lifecycle request is non-caller-constructible, but its port ownership is still self-asserted through public DisposableContextPortId::new(u64) plus DisposableContextPort::port_id(). Two distinct adapter instances can both report port_id=101; after A is bound, Browser Session's equality check will accept B as the same port. More strongly, A can relay the borrowed aggregate-issued create/destroy request to B, and B can satisfy the same scalar equality and reach remote lifecycle I/O even though B was never the aggregate-approved adapter instance. The current hostile test only uses 101 vs 102, so it proves mismatch rejection but not non-forgeable adapter ownership.

Required RED before this prerequisite can be GREEN: bind adapter A with id 101, then use distinct adapter B also claiming id 101; B must be rejected before create/destroy I/O and must not be able to consume/replay A's request. Do not repair this by documenting port-id uniqueness or randomizing a public scalar. The binding itself needs a non-caller-constructible Browser Session-approved/linear port capability or equivalent identity that a second adapter cannot self-select or replay. Keep remote BiDi identifiers outside Browser Session domain truth.

Separately, current hosted CI 34520503299 is still RED at rustfmt before tests/Clippy/rustdoc, and coverage measurement also fails; fix that operational RED without weakening gates after the authority model is corrected.

@seonghobae
seonghobae marked this pull request as ready for review September 10, 2026 19:37
@seonghobae
seonghobae marked this pull request as draft September 10, 2026 19:47

Copy link
Copy Markdown
Contributor Author

Exact-head RED evidence for 9caf9bbe4228c443b7d5a4279831765a6a38765a is now causal and reproducible. CI 34522121442 reached the Browser Session integration suite after repairing the pre-existing lifecycle-port test-signature drift. All earlier Browser Session unit/integration cases passed; only the new same-id/different-adapter hostile tests failed.

Coverage job 103021942177 observed:

  • distinct_port_with_same_claimed_id_cannot_create: actual Ok(PresentationMutationAuthority { browser_session: 17, isolation: "spoofed-isolation", browsing_context: 42, context_epoch: 2, ... }), expected Err(LifecyclePortMismatch).
  • distinct_port_with_same_claimed_id_cannot_destroy: actual Ok(()), expected Err(LifecyclePortMismatch).

So a distinct adapter B that self-reports the already-bound scalar port id can both reach create I/O and reach destroy I/O. This falsifies the current claim that aggregate-issued request + DisposableContextPortId binds lifecycle mutation to one adapter instance. Keep this RED until the port ownership is non-forgeable/non-replayable; do not weaken or delete the hostile fixture.

Repository status on this exact head is independently RED at cargo fmt --check as well. Rustfmt diagnostics artifact 10170079709, digest sha256:982aee39ad1383379288ff82575a950d0d72cbee6b2c28b8de62d15d10734068, remains available. Production coverage cannot proceed to exact enforcement while the intentional hostile tests fail. A separate nightly diagnostic also flags AtomicU64::fetch_update as deprecated in favor of try_update; treat that as root-fix work only after compatibility with the pinned stable toolchain is verified, not as a warning suppression exercise.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head security finding on 9caf9bbe4228c443b7d5a4279831765a6a38765a: the current port-binding preflight still cannot guarantee the stated “reject before lifecycle I/O” boundary because bind_lifecycle_port / require_bound_lifecycle_port call the public trait method DisposableContextPort::port_id(&self) before issuing or validating the aggregate request. Rust &self is not a purity guarantee: an implementation can use interior mutability, a socket/client handle, atomics, or delegated state and perform remote side effects from port_id(). The rustdoc requirement that port_id be side-effect-free is therefore a convention, not an enforced security invariant. The same problem remains even after replacing the scalar id with a harder-to-forge value if Browser Session must first invoke an arbitrary adapter callback to discover/validate that value.

Required RED: use a hostile port whose identity/preflight callback records a lifecycle side effect, then exercise (a) a first create attempt and (b) a mismatched destroy/create attempt that Browser Session is expected to reject. Both must show zero adapter/browser mutation before an aggregate-issued authority exists and is validated. Do not satisfy this by documenting port_id() purity or by testing only the reviewed WebDriver adapter implementation.

Causal direction: remove authority-sensitive identity discovery from an arbitrary preflight callback. Establish the lifecycle-port binding through a non-caller-constructible composition/binding capability (or a session-owned/linear port wrapper) before lifecycle mutation, then invoke the adapter only under the already validated create/destroy request. If DisposableContextPort remains externally implementable, the contract must not rely on &self implying side-effect freedom. Preserve the current same-id/different-adapter hostile RED and the requirement that requests cannot be replayed into a different bound adapter. Keep remote BiDi identifiers outside Browser Session domain truth.

@seonghobae
seonghobae marked this pull request as ready for review September 10, 2026 20:09
@seonghobae
seonghobae marked this pull request as draft September 10, 2026 20:13
@seonghobae
seonghobae marked this pull request as ready for review September 10, 2026 21:14

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact head 9cde981899950b900698a17e7fa739af59f6bb4f, especially the security/API claim that consuming BrowserSession + P into BoundBrowserSession<P> removes pre-authority adapter callbacks and ordinary distinct-adapter substitution/replay without moving protocol-specific BiDi ownership into the Browser Session domain.

@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Exact revision and PR diff summary =='
git rev-parse HEAD
git status --short
git diff --stat 6d87dff5dc572fbd74d06309d574a998f23cf02f..HEAD
git diff --name-only 6d87dff5dc572fbd74d06309d574a998f23cf02f..HEAD

printf '%s\n' '== Candidate lifecycle files =='
fd -t f -e rs . | while IFS= read -r file; do
  if rg -q -i 'BrowserSession|BoundBrowserSession|DisposableContextPort|bind_lifecycle_port|DisposableContext(Create|Destroy)Request' "$file"; then
    printf '%s\n' "$file"
  fi
done

printf '%s\n' '== Rust API outline for candidate files =='
fd -t f -e rs . | while IFS= read -r file; do
  if rg -q -i 'BrowserSession|BoundBrowserSession|DisposableContextPort|bind_lifecycle_port|DisposableContext(Create|Destroy)Request' "$file"; then
    ast-grep outline "$file" --items all --type struct,trait,impl,function,method --match 'BrowserSession|BoundBrowserSession|DisposableContextPort|bind_lifecycle_port|DisposableContextCreateRequest|DisposableContextDestroyRequest' || true
  fi
done

printf '%s\n' '== Lifecycle declarations, calls, and port-ID remnants =='
rg -n -C 4 --type rust \
  '(\bbind_lifecycle_port\s*\(|\b(create|destroy)_(disposable_)?context\s*\(|\bDisposableContextPort\b|\bBoundBrowserSession\b|\bDisposableContextPortId\b|\bport_id\s*\()' \
  .

printf '%s\n' '== Browser Session BiDi/protocol-specific references =='
rg -n -i -C 3 --type rust \
  '(bidi|webdriver|pending|quarantin|remote.liveness|remote_liveness|accepted)' \
  . \
  -g '!**/test/**' -g '!**/tests/**' || true

printf '%s\n' '== Documentation and external fixtures relevant to binding =='
fd -t f . | while IFS= read -r file; do
  if rg -q -i 'BoundBrowserSession|bind_lifecycle_port|DisposableContextPortId|distinct.adapter|hostile|linear.lifecycle|ADR.?.?0114' "$file"; then
    printf '%s\n' "$file"
  fi
done

Length of output: 147304


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Browser Session public types and lifecycle implementation =='
sed -n '1,720p' crates/originweave-browser-session/src/lib.rs

printf '%s\n' '== Focused external hostile tests =='
for file in \
  crates/originweave-browser-session/tests/lifecycle_port_authority.rs \
  crates/originweave-browser-session/tests/lifecycle_port_preflight_side_effect.rs \
  crates/originweave-browser-session/tests/lifecycle_port_same_id_spoof.rs \
  crates/originweave-browser-session/tests/sequential_incarnation_reuse.rs \
  crates/originweave-browser-session/tests/destroy_failure_requires_recovery.rs \
  tests/test_browser_session_lifecycle_contract.py
do
  printf '\n== %s ==\n' "$file"
  cat -n "$file"
done

printf '%s\n' '== Browser Session dependency boundary =='
browser_manifest="$(fd -t f '^Cargo\.toml$' crates/originweave-browser-session | head -n 1)"
printf '%s\n' "$browser_manifest"
cat -n "$browser_manifest"
printf '%s\n' '== Direct Browser Session crate imports =='
rg -n '^\s*use\s+originweave_[A-Za-z0-9_]+' crates/originweave-browser-session/src crates/originweave-browser-session/tests

printf '%s\n' '== Public mutable extraction and lifecycle helper reachability =='
rg -n -C 3 --type rust \
  '(pub(\([^)]*\))?\s+(fn|struct|enum|trait)|fn\s+(create|destroy)_disposable_context_with_port|fn\s+(into_|as_mut|lifecycle_port_mut)|&mut\s+(self|P)|->\s*&mut)' \
  crates/originweave-browser-session/src/lib.rs

printf '%s\n' '== Focused PR diff for implementation and contracts =='
git diff --find-renames --find-copies --unified=50 \
  6d87dff5dc572fbd74d06309d574a998f23cf02f..HEAD -- \
  crates/originweave-browser-session/src/lib.rs \
  tests/test_browser_session_lifecycle_contract.py \
  docs/adr/0114-browser-session-disposable-context-authority.md \
  docs/traceability/browser-session-lifecycle-authority.md \
  docs/uml/browser-session-lifecycle-authority.md

printf '%s\n' '== Changed documents =='
for file in \
  docs/adr/0114-browser-session-disposable-context-authority.md \
  docs/traceability/browser-session-lifecycle-authority.md \
  docs/uml/browser-session-lifecycle-authority.md
do
  printf '\n== %s ==\n' "$file"
  cat -n "$file"
done

Length of output: 215228


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Repository-wide lifecycle API references =='
rg -n -C 3 --type rust \
  '(\bcreate_disposable_context\s*\(|\bdestroy_disposable_context\s*\(|\bbind_lifecycle_port\s*\(|\blifecycle_port_mut\b|\binto_(inner|port|browser_session)\b|\bDisposableContextPortId\b|\bport_id\s*\()' \
  .

printf '%s\n' '== Public methods on BrowserSession and BoundBrowserSession =='
rg -n -U --type rust \
  'impl(?:<[^>]+>)?\s+(?:BrowserSession|BoundBrowserSession(?:<[^>]+>)?)\s*\{(?s:.*?)^\}' \
  crates/originweave-browser-session/src/lib.rs || true
rg -n -C 2 --type rust \
  'pub\s+(?:const\s+)?fn\s+(?:create_disposable_context|destroy_disposable_context|bind_lifecycle_port|lifecycle_port|browser_session|into_[A-Za-z_]+|[A-Za-z_]+_mut)\s*\(' \
  crates/originweave-browser-session/src/lib.rs

printf '%s\n' '== Trait implementation locations =='
rg -n -C 2 --type rust 'impl\s+(?:[A-Za-z0-9_<>:]+\s+for\s+)?DisposableContextPort\b' .

printf '%s\n' '== Patch whitespace integrity =='
git diff --check 6d87dff5dc572fbd74d06309d574a998f23cf02f..HEAD

printf '%s\n' '== Exact-head PR checks and review state =='
gh pr view 317 --repo ContextualWisdomLab/OriginWeave \
  --json number,headRefOid,isDraft,mergeStateStatus,reviewDecision,statusCheckRollup \
  --jq '{number, headRefOid, isDraft, mergeStateStatus, reviewDecision, checks: [.statusCheckRollup[] | {name, status, conclusion, workflowName}]}'

Length of output: 68284


@seonghobae Review complete for exact head 9cde981899950b900698a17e7fa739af59f6bb4f.

I found no code-level issue in the requested security/API change.

  • BrowserSession::bind_lifecycle_port(self, port) only moves the aggregate and port into private BoundBrowserSession<P> fields. It invokes no adapter callback.
  • The public lifecycle methods exist on BoundBrowserSession<P>. They accept no replacement port.
  • Repository-wide inspection found no DisposableContextPortId, port_id(), mutable port accessor, extraction method, or alternate public lifecycle entry point.
  • Create validates Active state and reserves the epoch before it calls the bound port.
  • Destroy validates state, session, incarnation, isolation, context, and epoch before it calls the bound port.
  • The request types have private fields and no public constructors.
  • The external hostile fixtures cover the pre-authority callback case, ordinary distinct-adapter substitution, sequential-incarnation replay, and recovery behavior.
  • originweave-browser-session depends only on originweave-core. It does not import or depend on the BiDi crate. The BiDi-specific pending → accepted/quarantined state and remote-liveness responsibility remain outside the Browser Session domain.

The structural binding prevents ordinary callers from substituting or replaying a different adapter after binding. It cannot prevent a malicious trusted DisposableContextPort implementation from internally forwarding an authorized call. ADR 0114 states this TCB boundary.

Exact-head verification is not complete. GitHub reports Rust contracts and Production coverage as queued, and the PR state is UNSTABLE. I cannot determine readiness or approve the PR until the required exact-head checks are terminal and GREEN.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge-blocking integration finding on exact 9cde981899950b900698a17e7fa739af59f6bb4f: the linear BoundBrowserSession<P> closes caller-side port substitution, but the lifecycle contract still has no aggregate→adapter completion phase for creation, and DisposableContextCreateRequest is not unique per create attempt. Every create request in one Browser Session incarnation exposes only the same (BrowserSessionId, BrowserSessionIncarnation) pair, even though BrowserSession already reserves a distinct context epoch before remote I/O. After port.create_disposable_context(&request) returns, the aggregate may accept the handle or reject it as duplicate/ambiguous and enter RecoveryRequired, but DisposableContextPort receives no accept/reject completion carrying a per-attempt identity. A BiDi adapter implementing #316 therefore cannot deterministically move the exact remote tuple from pending→accepted or pending→quarantine without inventing adapter-local correlation/authority, keying only by the same session/incarnation, relying on call order, or prematurely authorizing before Browser Session validation.

Required RED before adoption: one bound port produces at least two creation candidates in the same Browser Session incarnation, retains them as separate pending protocol tuples, and Browser Session accepts one while rejecting the other (e.g. duplicate domain context/isolation). The adapter must promote only the accepted candidate and quarantine exactly the rejected candidate; neither candidate may collide/overwrite because their create requests are indistinguishable. No remote tuple may become authorizing before aggregate acceptance.

Causal repair should extend the Browser Session-owned transaction boundary, not move BiDi ids into this domain: mint a non-caller-constructible per-attempt lifecycle identity/capability (the already-reserved epoch is a natural candidate if its semantics fit), pass it in the create request, and provide an aggregate-issued accept/reject completion that the exact bound port consumes. The adapter keeps protocol tuples pending until that completion. Preserve structural port binding and the existing no-preflight/same-id hostile tests. #316 remains the owner of remote BiDi pending/accepted/quarantine data; #317 should only provide the domain transaction identity/completion contract it needs.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional lifecycle-ordering acceptance from stacked #318 exact f936f8b8259e096bd4fb850756245cda99c583cc: when record_transport_loss() has already moved Browser Session to TransportLost (or an ownership failure has moved it to RecoveryRequired), a buffered/replayed browser navigation observation must not be accepted as new current-state evidence. record_observed_navigation(context) should gate on aggregate state before context/presentation state, fail closed as SessionNotActive, perform zero adapter I/O, leave the exact recovery-evidence set unchanged, and never revive a retained pre-loss authority. This closes a realistic asynchronous event-ordering race: the dead/untrusted transport is not allowed to advance presentation lifecycle after recovery handoff has begun. The RED is test-only on #318; no #317 source mutation here.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up Browser Session acceptance from #318 exact 6bfd0d085145096756332f270e1fa5e803fdcbf3: when the causal source repair lands here, aggregate trust state must gate the lower-level context state before ownership lookup/idempotency. In RecoveryRequired, reestablish_presentation_authority(context) and ordinary owned-context cleanup retry must fail specifically as SessionNotActive, while state remains RecoveryRequired and the existing unproven-destruction evidence is unchanged; ContextNotOwned would falsely prove lifecycle ownership was consumed. Likewise, a buffered/replayed observed-navigation after TransportLost must return SessionNotActive and leave the aggregate exactly TransportLost with identical recovery evidence. CodeRabbit independently verified these RED refinements on #318. This is additive to the existing exact handle+epoch and same-adapter recovery blockers, not a replacement for them.

Copy link
Copy Markdown
Contributor Author

#318 successor exact c7117e9593940e8412b29b622db38ede0f551678 adds a narrower aggregate-ordering RED: once Browser Session is TransportLost or RecoveryRequired, record_observed_navigation(foreign_context) must return SessionNotActive before per-context ownership lookup, with zero adapter I/O and exact state/recovery-evidence preservation. This closes a differential ContextNotOwned ownership oracle on an already-untrusted aggregate. Canonical #317 repair should therefore gate aggregate trust/recovery state before any raw BrowsingContextId ownership/idempotency lookup; the selector is meaningful only while the aggregate is Active.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact fbfed17af630113af5aaec9d1af37790fae77a4e adds the remaining aggregate-trust ordering RED for the normal terminal state. After an owned disposable context is proven destroyed and Browser Session reaches Ended, both the formerly-owned BrowsingContextId and a foreign raw selector must be rejected by record_observed_navigation(...) as SessionNotActive before any ownership/history lookup, with zero adapter I/O, exact Ended state preserved, and recovery evidence unchanged. This closes the same ownership-oracle class already covered for TransportLost and RecoveryRequired: the navigation transition is valid only while the aggregate is Active; Ended must gate before per-context lookup/idempotency. No #317 source mutation is requested from this test lane.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact e0407dd9314e9eface64cad10a0b03dda8e0b096 adds one missing lifecycle-ordering RED for the #317 production repair: after observed-navigation invalidation and proven destroy_owned_disposable_context(context), the Browser Session remains reusable/Active, but a buffered/late navigation notification for that now-consumed context must be rejected as ContextNotOwned before adapter I/O. The rejection must not manufacture recovery evidence or consult historical tombstones in a way that resurrects presentation/lifecycle ownership. An unrelated foreign selector has the same active-state rejection semantics. This is intentionally distinct from the existing Ended/TransportLost/RecoveryRequired aggregate-first gate: while the aggregate is still Active, lifecycle ownership consumption is authoritative and historical ownership must not become observation authority. Please preserve this in the causal #317 state-model repair together with bounded hot ownership state.

@seonghobae seonghobae added bug Something isn't working priority: high labels Sep 12, 2026 — with ChatGPT Codex Connector

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact 79e8db01f8ccf8297a2855dad9254d3db33a03f9 exposes one additional Browser Session blocker before the navigation-invalidation successor can turn GREEN. A raw-id-only record_observed_navigation(BrowsingContextId) cannot distinguish a delayed replay from a distinct later navigation once presentation authority has been explicitly re-established. The old event can therefore invalidate the newly current authority.

Required causal repair: bind the observed-navigation transition to the exact current BrowserContextEpoch. While aggregate state is Active, matching current epoch invalidates; duplicate delivery of the same already-invalidated epoch is idempotent; after reestablish_presentation_authority advances the epoch, replay of the prior epoch must return AuthorityMismatch with zero adapter I/O and leave current authority usable. Inactive aggregate gates (Ended/TransportLost/RecoveryRequired) remain higher priority, and consumed ownership remains ContextNotOwned.

This is generation correlation, not adapter authority. #316 still owns WebDriver BiDi event identity/replay qualification; Browser Session owns deterministic acceptance of the supplied domain generation. Please include this with the existing operation/destroy epoch-correlation repair rather than exposing another raw selector transition.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up Browser Session finding from #318 exact 8f1678f09017387c3f6d2d0aaedbaa83cbdd0489: observed-navigation epoch correlation must be scoped to the exact owned context, not merely to a session-global epoch namespace. A sibling owned context's current BrowserContextEpoch supplied with another context id must fail AuthorityMismatch before any adapter I/O and must not invalidate either context. The new RED creates two simultaneously owned contexts, proves their epochs differ, attempts cross-context epoch substitution, then verifies both authorities remain usable before applying the exact matching navigation to only one context. Production repair should compare (BrowsingContextId, current BrowserContextEpoch, presentation-validity state) as one context-generation fact after aggregate trust gating; do not accept “any issued/current epoch in this BrowserSession” as sufficient navigation provenance.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact da9c8f1b3e9ec61a6f75d6e6e5058426ded07820 adds a hostile cross-session navigation-generation RED. Current desired (BrowsingContextId, BrowserContextEpoch) correlation aliases across independent Browser Session aggregates because each aggregate can reuse the same external BrowserSessionId/BrowsingContextId and allocate the same local epoch value. The process-local BrowserSessionIncarnation is the existing discriminator and is already carried by lifecycle/operation authority. Minimum source repair should gate aggregate activity, then reject observed-navigation provenance whose incarnation differs from self.incarnation before any context lookup or adapter I/O; only after that validate exact context+epoch/presentation state. The incarnation remains non-authorizing provenance. Do not solve this by making epochs globally mutable/shared or by moving authority into the BiDi adapter.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact da28f1d64df2c521a9537bf8a70155f75effa920 found one additional source blocker in the navigation authority model: invalidation on navigation start is not enough if reestablish_presentation_authority may succeed immediately afterward. WebDriver BiDi exposes distinct navigationStarted and navigationCommitted events (plus fragmentNavigated, navigationAborted, navigationFailed), so a start observation does not prove the new document transition has settled.

Required Browser Session behavior: after exact incarnation+context+epoch navigation start, presentation remains invalidated and re-establishment must fail zero-I/O until a matching generation is explicitly settled. Settlement must be a zero-I/O domain transition, consume no authority epoch, and stale settlement from an older epoch/incarnation must fail AuthorityMismatch. Proven lifecycle cleanup must remain possible while navigation is still pending. #316 should own protocol event-id/replay matching; core should consume only a qualified settled-generation transition and must not treat W3C navigation ids as mutation authority.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up from #318 exact 56b3d41ee514ada9c9c02b01313c3996ef0b2f89: the new settlement transition needs the same non-aliasing checks as navigation-start invalidation, not merely the same method signature. Hostile RED now proves that an invalidated context cannot be marked settled by a sibling context's epoch, and that a prior BrowserSessionIncarnation cannot settle a current aggregate even when raw session/context/epoch values numerically alias. Both cases must fail AuthorityMismatch, perform zero adapter I/O, and keep re-establishment closed until the exact current incarnation+context+epoch is settled. This should be implemented in the Browser Session aggregate before any new authority epoch is issued.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact b72ab18593f1fe0b622bccca4f1e39c254bb50d8 found a stricter Browser Session authority defect in the proposed navigation-settlement contract. Treating (BrowserSessionIncarnation, BrowsingContextId, BrowserContextEpoch) as non-authorizing provenance but allowing record_observed_navigation_settled(incarnation, context, epoch) to unlock presentation re-establishment makes the reconstructible tuple a de facto capability.

Production acceptance should instead have Browser Session validate the exact start provenance and issue an opaque, non-constructible NavigationSettlementAuthority bound to that aggregate/pending generation. record_observed_navigation_settled must validate that aggregate-issued witness rather than accept raw ids. Cross-aggregate witness substitution must fail AuthorityMismatch before I/O even when external session/context ids and local epoch values alias. W3C NavigationInfo.navigation remains adapter correlation evidence, never Browser Session policy authority.

Also correcting my earlier standards handoff: the canonical W3C TR page currently identifies 9 September 2026 as the published Working Draft (WD-webdriver-bidi-20260909) and links the Editor’s Draft separately. The prior 24-August objection should not block this PR. Runtime qualification remains a separate explicit decision.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact successor found one remaining Browser Session availability/security gap in the navigation state machine. The 9 Sep 2026 WebDriver BiDi WD has distinct terminal browsingContext.navigationAborted and browsingContext.navigationFailed events carrying NavigationInfo; a design that only accepts commit/fragment as settlement leaves an admitted navigationStarted generation permanently pending after abort/failure. That is a fail-closed state which can become a durable self-DoS, not a safe terminal policy state.

Production acceptance should therefore distinguish successful settlement from negative terminal observation. After aggregate trust and exact opaque pending-navigation witness validation, an equivalent of record_observed_navigation_terminated(&NavigationSettlementAuthority, NavigationTerminationOutcome::{Aborted, Failed}) must consume that pending witness with zero adapter I/O. It must NOT silently mint or restore PresentationMutationAuthority; the pre-navigation authority stays stale. If lifecycle ownership remains active, explicit bound-owner re-establishment may then issue one fresh epoch so the still-usable context is not stranded. Replaying the consumed witness must be AuthorityMismatch and must not affect a newer generation. Raw session/context/epoch or W3C navigation ids remain evidence, not policy authority.

#318 now carries the repository contract plus hostile failure/abort RED on its current lineage. Please implement this together with the existing navigation-pending / settlement / re-establishment repair rather than treating abort/failure as an adapter-only no-op.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact c5e0cb445758747b57c117220ccfe13ebf9bd5de adds a new hostile Browser Session RED: one aggregate-issued NavigationSettlementAuthority must admit exactly one terminal outcome across the positive and negative APIs, not once per API. After record_observed_navigation_settled(&witness) succeeds, record_observed_navigation_terminated(&witness, Failed|Aborted) must be zero-I/O AuthorityMismatch; after negative termination succeeds, later positive settlement of the same witness must likewise fail. Otherwise separate consumption ledgers/state paths let reordered or duplicated BiDi terminal evidence rewrite an already-terminal navigation generation. Production acceptance should use one shared pending-navigation record/witness consumption state for committed/fragment-settled and failed/aborted outcomes, with terminal outcome assignment monotonic and single-use. This is core policy-state protection; #316 remains responsible for protocol correlation/replay filtering.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact 13454f971c0d8de7a290a08b0c39d01ecd3e57b7 found one additional production invariant for this owner lane: NavigationSettlementAuthority lifetime must be bounded by aggregate trust, not only by pending-navigation terminal assignment. If a witness was issued while Active and the aggregate later becomes TransportLost, RecoveryRequired, or Ended, both record_observed_navigation_settled and record_observed_navigation_terminated must gate on aggregate state first and return SessionNotActive before witness validation/adapter I/O. Inactive terminal replay must not consume the witness, rewrite recovery evidence, or reopen presentation re-establishment. This prevents an adapter-buffered capability from outliving the Browser Session trust boundary.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact a6ca33234eefdfcb6380363e60089940e01cf67b exposes one additional production invariant for this owner lane: a pending navigation witness must not outlive the exact context ownership that existed when it was minted.

The aggregate may still be Active after proven destruction, so require_active() alone is insufficient. For both positive settlement and negative termination, the causal validation order should remain fail-closed: aggregate trust → opaque pending-witness identity → witness-bound context/generation is still live-owned → exactly-once terminal assignment. Once proven destruction consumes the context, any pre-destroy pending witness must fail zero-I/O as ContextNotOwned; it must not mutate historical tombstones/pending state, create recovery evidence, or permit later presentation re-establishment. This complements the existing TransportLost/RecoveryRequired/Ended witness-death rule rather than replacing it.

Please make context destruction and pending-navigation capability invalidation one monotonic aggregate transition (or an equivalent invariant with no observable gap). Do not preserve the pending witness merely because the aggregate remains reusable for other contexts.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact ea85d9f8ec60b87a70cc32ded7bfb02071a3c03f adds one more Browser Session RED: a second adapter-qualified navigationStarted for the same owned context may arrive before the first navigation reaches any terminal event. Core cannot treat the first pending witness as the only admissible generation, because a newer navigation can supersede it while presentation authority is already invalidated. The second qualified start should issue a new opaque pending witness without minting a presentation epoch; the prior witness becomes permanently superseded. Late commit/abort/failure for the superseded witness must be zero-I/O AuthorityMismatch and must not permit reestablish_presentation_authority while the newer navigation remains pending. Only the current pending witness may settle/terminate and allow explicit fresh authority. Adapter replay/identity qualification remains #316; Browser Session should not consume the W3C navigation id as policy authority.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 current acceptance repair adds two production blockers that should remain explicit in the #317 Browser Session state machine. First, positive settlement must be scoped to the exact current pending witness/context: settling context A must not clear context B's pending navigation even within the same aggregate. Second, same-context supersession must reject both late negative and late positive terminal evidence for navigation A while navigation B is pending; checking only Pending plus aggregate/context is insufficient. The production order should remain aggregate trust gate → exact current pending-witness identity/context → exact live ownership → single terminal assignment. #318 exact 7a4ec9a6398d3583c76dd668d052a596613c325b contains independent REDs for sibling positive settlement and superseded positive settlement. No #317 source mutation is requested from this test-lane writer.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact successor d89695f3c84c5538b9bb2e29e6b91d08ba54a572 adds a distinct Browser Session RED for a browser navigation that starts after the prior navigation has terminally settled but before the owner has re-established presentation authority. This race is different from overlapping-pending supersession: the context remains live, no new presentation epoch has been minted, and the browser can move again before control-plane reauthorization. Production must accept the later adapter-qualified start against the still-current invalidated context generation, issue a fresh current pending witness without spending an epoch, and keep re-establishment closed until that latest witness reaches a terminal outcome. Otherwise the prior terminal event can authorize a document that has already navigated again, or the aggregate can reject legitimate browser lifecycle evidence and drift from remote state. Preserve aggregate trust and exact live ownership gates; stale prior terminal replay remains zero-I/O AuthorityMismatch.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 acceptance handoff from exact 575496c6276c50bd2d3f3e98a3f230811a688619: the state machine must admit a later adapter-qualified navigation start not only after positive settlement but also after NavigationTerminationOutcome::{Failed, Aborted} when presentation authority has not yet been explicitly re-established. The later start stays on the same live invalidated context generation, spends no presentation epoch, issues a new opaque pending witness, and makes the consumed prior witness permanently dead across both positive and negative terminal APIs. Re-establishment remains AuthorityMismatch until the latest witness reaches its own terminal outcome. This prevents a negative terminal from becoming an availability barrier or from authorizing the document after a newer navigation has already started. Preserve ordering: aggregate trust → exact live context generation/ownership → current pending witness identity → exactly-once terminal assignment → explicit re-establishment.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage execution from stacked #318 exact 6e73ca40a5a17a925223212c18e4396b6c2433ec reached the production compile path and surfaced a real deprecation in this #317 source head under the repository's pinned coverage nightly (nightly-2026-08-18, rustc 1.100.0-nightly): AtomicU64::fetch_update is deprecated in favor of try_update at crates/originweave-browser-session/src/lib.rs:544 and :1021. This is production-owner work, so I did not mutate #317 from the #318 acceptance lane. Please root-fix both call sites (preserving ordering and closure semantics) in this owner branch rather than suppressing the warning; rerun exact-head tests/Clippy/rustdoc/coverage afterward. Evidence: Actions run 34724287752, Production coverage job 103635577342.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fresh #318 RED found a remaining Browser Session authority bypass requirement. advance_context_epoch(context) is currently a public generic authority-minting path on both BrowserSession and BoundBrowserSession. Pending-navigation RED already required it to fail after navigationStarted, but terminal handling had not proved that the path stays closed after a matching positive settlement or typed Failed/Aborted terminal and before explicit reestablish_presentation_authority.

#318 exact successor 5eedff35e9babb06538952429a64d23be5e9e35d now requires advance_context_epoch to return AuthorityMismatch with zero adapter I/O in all of those settled/terminated-but-not-reestablished states, including terminal-then-later-navigation flow. This matters because settlement/termination intentionally spends no presentation epoch; letting generic rotation mint at that point makes the explicit re-establishment gate advisory rather than authoritative.

Production repair in this owner lane should make navigation-invalidated state dominate every presentation-authority mint path: advance_context_epoch may remain usable for an established context if other bounded contexts require it, but it must not transition a navigation-invalidated context back to authority. Only exact current terminal assignment followed by explicit reestablish_presentation_authority may consume the next aggregate epoch. Preserve aggregate-wide monotonic epoch allocation and zero adapter I/O. No #317 source was mutated from #318.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318 exact successor c454ee2fec48473fb0f4655b6c670310ab448f01 found a remaining Browser Session invariant: one terminal navigation transition must authorize at most one explicit reestablish_presentation_authority(context) call. After the first successful re-establishment, a duplicate call with no newer navigation must return AuthorityMismatch, perform zero adapter I/O, spend no aggregate epoch, and leave the current presentation authority intact. A later genuinely observed navigation may then invalidate that current generation and, after its own matching terminal outcome, permit exactly one new re-establishment. Please implement this as consumption of the navigation-invalidated/re-establishment eligibility in the #317 aggregate state machine rather than as a caller-side guard. Generic advance_context_epoch remains a separate established-context operation and must not be used as navigation re-establishment. #318 adds hostile RED navigation_reestablishment_is_single_use.rs plus doctoring for this boundary.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#318에서 추가로 확인된 Browser Session owner finding입니다. 현재 acceptance는 terminal outcome 뒤 explicit re-establishment eligibility가 생기지만, 그 사이 exact context가 proven destruction되면 그 eligibility도 ownership과 함께 소멸해야 합니다. settled/terminated → destroy_owned_disposable_context(context) → reestablish_presentation_authority(context)ContextNotOwned, zero additional adapter I/O여야 하며 aggregate는 Active, recovery evidence는 불변이어야 합니다. 그렇지 않으면 이미 파괴된 context를 과거 navigation terminal transition으로 다시 authority-mint하는 resurrection 경로가 생깁니다. Production state machine에서 terminal-derived re-establishment eligibility를 context ownership과 별도 dangling flag로 두지 말고 exact live ownership record에 종속시키는 쪽이 causal fix입니다. #318 commits f9110ecf7d756693f8161f9762ee82de9ad4f72c + ad5ec10762bc29e63adb55bf8edd6077b5f216c9에 positive/negative RED와 doctoring을 추가했습니다.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New Browser Session owner finding from #318 acceptance lane: terminal-derived reestablish_presentation_authority eligibility must die when aggregate trust is lost after the terminal event but before re-establishment. Exact #318 RED lineage starts at a2fe20fcdd8e10df94391b3b276b84a43d3bf735; doctoring is synchronized at 7472e6611b0a3136f975b1ab0bcb00236acef205.

Production invariant: after a matching positive settlement, record_transport_loss() must dominate the prior terminal transition; reestablish_presentation_authority(context) must return SessionNotActive, perform zero adapter I/O, leave TransportLost unchanged, and preserve recovery evidence. Likewise, after a matching negative terminal, an unproven destroy that moves the aggregate to RecoveryRequired must make the prior re-establishment eligibility unusable; the same call must return SessionNotActive and preserve exact recovery evidence.

Please bind terminal-derived re-establishment eligibility to aggregate active trust, not to a dangling per-context boolean. This complements the existing exact-context-ownership finding: both aggregate trust and live ownership must dominate eligibility before any fresh presentation epoch can be minted. #318 remains test-first and does not modify this production branch.

Copy link
Copy Markdown
Contributor Author

Follow-up to review 5189274520: CodeRabbit correctly noted that Failed and Aborted are separate typed terminal outcomes. #318 exact 2dc9cfe3d165a895a71bc111dcaf4f2627fe658e now covers Aborted against both aggregate trust-loss paths: TransportLost and unproven-destruction RecoveryRequired. Both require SessionNotActive, zero post-loss adapter I/O, unchanged inactive state, and exact recovery-evidence preservation. Production should apply one aggregate-active gate to all terminal-derived re-establishment eligibility rather than outcome-specific branches.

Copy link
Copy Markdown
Contributor Author

#318에서 새 Browser Session RED를 추가했습니다. Exact child commits 811e6a39d22d15279d37528622430e62c9ded3854a4051f612a1374709dabb80095eef1c8dcffbcevalid terminal 이후 explicit re-establishment 전 들어온 invalid navigation observation이 기존 terminal-derived eligibility를 지우지 못해야 한다는 경계를 고정합니다.

Production invariant: record_observed_navigation은 incarnation/context/context-epoch provenance를 state mutation보다 먼저 검증해야 합니다. terminal-derived re-establishment eligibility가 이미 존재하는 context에 sibling/stale epoch 같은 invalid start가 들어오면 AuthorityMismatch, zero adapter I/O, zero epoch consumption으로 끝나야 하고, 직전의 valid positive settlement 또는 typed negative terminal이 만든 1회 re-establishment opportunity는 그대로 남아야 합니다. 잘못된/지연된 browser evidence가 legitimate terminal transition을 지워 DoS를 만들 수 없게 해 주세요.

#318은 #317 source를 수정하지 않았습니다. #317의 monotonic state machine에서 validate provenance → admit/supersede only valid start → terminal eligibility 순서를 보장하는 causal repair가 필요합니다.

Copy link
Copy Markdown
Contributor Author

#318 follow-up on exact #317 head f73cc5def267b99f43986cd3c504b86cb3d489d7: a valid current terminal transition can create one explicit re-establishment opportunity, but a delayed terminal witness minted by a prior Browser Session incarnation must not be allowed to consume or clear that opportunity. #318 exact 18d6ba7beab263bb71f96955d12de9c21168b063 now exercises both stale positive settlement and stale typed negative termination after the current witness has already reached its valid terminal outcome; both must return AuthorityMismatch, perform zero adapter I/O, and leave the current aggregate eligible for exactly one reestablish_presentation_authority. Causal production ordering should therefore be active-trust/live-ownership validation → exact witness incarnation/context/generation validation → only then terminal-state mutation/eligibility consumption. Invalid terminal evidence must be observationally non-mutating, just like invalid navigation-start provenance.

Copy link
Copy Markdown
Contributor Author

#318 후속 RED에서 cross-context ordering gap을 추가로 고정했습니다. 새 exact successor는 #318 1fe8f1fde95915f772998bd17fc5a239e734d918입니다.

검증해야 할 production invariant는 terminal(A) -> navigationStarted(B) 순서입니다. A가 valid terminal을 받아 explicit re-establishment eligibility를 가진 뒤 sibling context B가 navigation-pending으로 들어가도, B start는 A의 eligibility를 지우거나 aggregate epoch을 소비하면 안 됩니다. A는 B가 pending인 동안에도 자기 terminal-derived eligibility를 한 번 소비해 fresh authority를 받아야 하고, B는 own terminal 전까지 계속 AuthorityMismatch여야 합니다. 이후 B terminal 뒤 re-establishment가 aggregate-wide next epoch을 받아야 합니다.

이는 per-context navigation lifecycle state가 aggregate-wide single pending/terminal flag로 축약되면 깨지는 경계입니다. #317 causal implementation은 live ownership record별 pending/terminal/re-establishment eligibility를 독립적으로 유지하고, aggregate-wide allocator는 explicit authority mint에서만 단조 증가하도록 해 주세요. record_observed_navigation/terminal observation 자체는 epoch을 소비하지 않아야 합니다.

#318 production source는 건드리지 않았고 test/doctoring만 추가했습니다. #317 exact head가 바뀌기 전까지 restack하지 않습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working priority: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant